// ============================================================================
// FileName: SIPDialPlan.cs
//
// Description:
// Converts an Asterisk like SIP dial plan into a useable form for the Proxy server.
//
// Author(s):
// Aaron Clauson
//
// History:
// ??     2006  Aaron Clauson   Created.
// 26 Aug 2007  Aaron Clauson   Added the ability to set the From header and the send to socket in a dial plan command.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
// ============================================================================

using System;
using System.Collections;
using System.Text.RegularExpressions;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace CN.VoIP.SIPServerCores.StatefulProxy
{
	public enum SIPDialPlanOpsEnum
	{
		Unknown = 0,
		Equals = 1,
		Regex = 3,
	}
	
	public class SIPDialPlanOpTypes
	{
		public static SIPDialPlanOpsEnum GetDialPlanOp(string dialPlanOp)
		{
			if(dialPlanOp == "=" || dialPlanOp == "==" || dialPlanOp == "=>")
			{
				return SIPDialPlanOpsEnum.Equals;
			}
			else if(dialPlanOp == "=~")
			{
				return SIPDialPlanOpsEnum.Regex;
			}
			else
			{
				return SIPDialPlanOpsEnum.Unknown;
			}
		}
	}

	public class SIPDialPlanCommand
	{
		public string Destination;
		public int Priority;
		public SIPDialPlanOpsEnum Operation;
		public string Data;
		public SIPSwitchCommand SwitchCommand;

		public SIPDialPlanCommand(SIPDialPlanOpsEnum opType, string dst, int priority, string data)
		{
			Operation = opType;
			Destination = dst;
			Priority = priority;
			Data = data;
			SwitchCommand = new SIPSwitchCommand(data);
		}
	}

	public class SIPSwitchCommand
	{
		public string Username;         // The username that will be used to authenticate a SIP call.
        public string Password;         // The password that will be used to authenticate a SIP call.
		public string Forward;          // The SIP URI that teh call will be forwarded as.
        public string FromHeader;       // An optional setting that allows the From header of the forwarded call to be set.
        public string SendToSocket;     // An optional setting that allows the send to socket for the call to be set (such as when an outbound proxy is required).

		public SIPSwitchCommand(string commandString)
		{
			int startBracketIndex = commandString.IndexOf("(");
			int endBracketIndex = commandString.IndexOf(")");
			int dataLength = endBracketIndex - startBracketIndex - 1;

			string dataString = commandString.Substring(startBracketIndex + 1, dataLength);

			string[] dataFields = dataString.Split(new char[]{','});

			Username = dataFields[0].Trim().Trim(new char[]{'"', '\''});
			Password = dataFields[1].Trim().Trim(new char[]{'"', '\''});
			Forward = dataFields[2].Trim().Trim(new char[]{'"', '\''});

            if (dataFields.Length > 3 && dataFields[3] != null)
            {
                FromHeader = dataFields[3].Trim().Trim(new char[] { '"', '\'' });
            }

            if (dataFields.Length > 4 && dataFields[4] != null)
            {
                SendToSocket = dataFields[4].Trim().Trim(new char[] { '"', '\'' });
            }
		}

		public SIPSwitchCommand(string username, string password, string forward)
		{
			Username = username;
			Password = password;
			Forward = forward;
		}
	}

	
	/// <summary>
	/// Dial plan is in the form:
	/// 
	/// exten =	 100,1,Switch(anonymous.invalid,,612@freeworlddialup.com)
	/// exten =~ 101,1,Switch(anonymous.invalid,,303@sip.blueface.ie)
	/// 
	/// </summary>
	public class SIPDialPlan
	{
		public const string DIALPLAN_LINESTART_KEY = "exten";
		
		private static ILog logger = log4net.LogManager.GetLogger("sipproxy");

		private SIPDialPlanCommand[] m_commands;
		public SIPDialPlanCommand[] Commands
		{
			get{ return m_commands; }
		}
		
		public SIPDialPlan(string dialPlan)
		{
			if(dialPlan != null)
			{
				string[] dialPlanEntries = dialPlan.Split(new char[]{'\n'});
				ParseDialPlan(dialPlanEntries);
			}
		}

		public SIPDialPlan(string[] dialPlanEntries)
		{
			if(dialPlanEntries != null)
			{
				ParseDialPlan(dialPlanEntries);
			}
			else
			{
				throw new ApplicationException("An empty dial plan was passed to SIPDialPlan, dialplan not created.");
			}
		}

		private void ParseDialPlan(string[] dialPlanEntries)
		{
			try
			{
				ArrayList dialPlanCommands = new ArrayList();
				
				if(dialPlanEntries != null)
				{
					foreach(string line in dialPlanEntries)
					{
						if(line == null || line.Trim().Length == 0 || line.Trim().StartsWith(";"))
						{
							// Do nothing, blank or comment line.
						}
						else
						{
							if(line.Trim().StartsWith(DIALPLAN_LINESTART_KEY))
							{
								// Split the line on the exten => data to get the data now that the line is recognised as a valid extension line.
								string op = Regex.Match(line, @"exten\s*(?<op>[=~>]+).+").Result("${op}");
								string[] lineFields = Regex.Split(line, op);

								if(lineFields == null || lineFields.Length <= 1)
								{
									logger.Warn("Dial plan line was invalid ignored: " + line);
								}
								else
								{
									string[] dataFields = lineFields[1].Trim().Split(new char[] {','}, 3);

									if(dataFields == null || dataFields.Length <= 2)
									{
										logger.Warn("Dial plan line was invalid ignored: " + line);
									}
									else
									{
										SIPDialPlanCommand command = new SIPDialPlanCommand(SIPDialPlanOpTypes.GetDialPlanOp(op), dataFields[0], Convert.ToInt32(dataFields[1]), dataFields[2]);
										dialPlanCommands.Add(command);
									}
								}
							}
							else
							{
								logger.Warn("Unknown dial plan command ignored: " + line);
							}
						}
					}
				}

				if(dialPlanCommands.Count > 0)
				{
					m_commands = (SIPDialPlanCommand[])dialPlanCommands.ToArray(typeof(SIPDialPlanCommand));
				}
			}
			catch(Exception excp)
			{
				logger.Error("Exception ParseDialPlan. " + excp.Message);
				throw excp;
			}
		}

		#region Unit testing.

		#if UNITTEST

		[TestFixture]
		public class SIPDialPlanUnitTest
		{			
			[TestFixtureSetUp]
			public void Init()
			{	}

			[TestFixtureTearDown]
			public void Dispose()
			{			
				
			}

			[Test]
			public void SampleTest()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				Assert.IsTrue(true, "True was false.");

				Console.WriteLine("---------------------------------"); 
			}
			
			[Test]
			public void SimpleDialPlanTest()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				string testDialPlan = @"
					exten => 100,1,Switch(""anonymous.invalid"", ""password"", ""612@freeworlddialup.com"")
					exten => 101,1,Switch(""username"", ""password"", ""303@sip.blueface.ie"")
				";

				SIPDialPlan dialPlan = new SIPDialPlan(testDialPlan);

				Console.WriteLine("dst=" + dialPlan.Commands[0].Destination + ", data=" + dialPlan.Commands[0].Data + ".");
				Console.WriteLine("dst=" + dialPlan.Commands[1].Destination + ", data=" + dialPlan.Commands[1].Data + ".");

				Assert.IsTrue(dialPlan.Commands.Length == 2, "The dial plan was not correctly parsed.");
				Assert.IsTrue(dialPlan.Commands[0].SwitchCommand.Username == "anonymous.invalid", "Command 1 username was incorrect.");
				Assert.IsTrue(dialPlan.Commands[0].SwitchCommand.Password == "password", "Command 1 password was incorrect, " + dialPlan.Commands[0].SwitchCommand.Password + ".");
				Assert.IsTrue(dialPlan.Commands[0].SwitchCommand.Forward == "612@freeworlddialup.com", "Command 1 forward was incorrect, " + dialPlan.Commands[0].SwitchCommand.Forward + ".");
				Assert.IsTrue(dialPlan.Commands[1].SwitchCommand.Username == "username", "Command 2 username was incorrect.");
				Assert.IsTrue(dialPlan.Commands[1].SwitchCommand.Password == "password", "Command 2 password was incorrect.");
				Assert.IsTrue(dialPlan.Commands[1].SwitchCommand.Forward == "303@sip.blueface.ie", "Command 2 forward was incorrect, " + dialPlan.Commands[1].SwitchCommand.Forward + ".");

				Console.WriteLine("---------------------------------"); 
			}	

			[Test]
			public void CommentOnLineTest()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				string testDialPlan = @"
					exten => 100,1,Switch(anonymous.invalid"", ""password"", ""612@freeworlddialup.com) ; Comment
					exten => 101,1,Switch(""username"", ""password"", ""303@sip.blueface.ie)
				";

				SIPDialPlan dialPlan = new SIPDialPlan(testDialPlan);

				Console.WriteLine("dst=" + dialPlan.Commands[0].Destination + ", data=" + dialPlan.Commands[0].Data + ".");
				Console.WriteLine("dst=" + dialPlan.Commands[1].Destination + ", data=" + dialPlan.Commands[1].Data + ".");

				Assert.IsTrue(dialPlan.Commands.Length == 2, "The dial plan was not correctly parsed.");
				Assert.IsTrue(dialPlan.Commands[0].SwitchCommand.Username == "anonymous.invalid", "Command 1 username was incorrect.");
				Assert.IsTrue(dialPlan.Commands[0].SwitchCommand.Password == "password", "Command 1 password was incorrect, " + dialPlan.Commands[0].SwitchCommand.Password + ".");
				Assert.IsTrue(dialPlan.Commands[0].SwitchCommand.Forward == "612@freeworlddialup.com", "Command 1 forward was incorrect, " + dialPlan.Commands[0].SwitchCommand.Forward + ".");
				Assert.IsTrue(dialPlan.Commands[1].SwitchCommand.Username == "username", "Command 2 username was incorrect.");
				Assert.IsTrue(dialPlan.Commands[1].SwitchCommand.Password == "password", "Command 2 password was incorrect.");
				Assert.IsTrue(dialPlan.Commands[1].SwitchCommand.Forward == "303@sip.blueface.ie", "Command 2 forward was incorrect, " + dialPlan.Commands[1].SwitchCommand.Forward + ".");

				Console.WriteLine("---------------------------------"); 
			}	

			[Test]
			public void DifferentOperatorsDialPlanTest()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				string testDialPlan = @"
					exten == 100,1,Switch(""anonymous.invalid"", ""password"", ""612@freeworlddialup.com"")
					exten =~ 101,1,Switch(""username"", ""password"", ""303@sip.blueface.ie"")
					exten = 103,1,Switch(""username"", ""password"", ""303@sip.blueface.ie)
				";

				SIPDialPlan dialPlan = new SIPDialPlan(testDialPlan);

				Assert.IsTrue(dialPlan.Commands.Length == 3, "The dial plan was not correctly parsed.");
				Assert.IsTrue(dialPlan.Commands[0].Operation == SIPDialPlanOpsEnum.Equals, "Command 1 operation was incorrect.");
				Assert.IsTrue(dialPlan.Commands[1].Operation == SIPDialPlanOpsEnum.Regex, "Command 2 operation was incorrect.");
				Assert.IsTrue(dialPlan.Commands[2].Operation == SIPDialPlanOpsEnum.Equals, "Command 3 operation was incorrect.");

				Console.WriteLine("---------------------------------"); 
			}	
		}

		#endif

		#endregion
	}
}
